﻿using System;
using System.Collections.Generic;
using Needle.Utils.Collections;
using SCOM = System.Collections.ObjectModel;
using System.Linq;

namespace Needle.Utils.Extensions
{
    /// <summary>
    /// IEnumerable&lt;T&gt; Extensions
    /// </summary>
    public static class IEnumerableExtensions
    {
        /// <summary>
        /// Returns a read-only shallow copy of the collection.
        /// </summary>
        public static SCOM.ReadOnlyCollection<T> AsReadOnly<T>(this IEnumerable<T> items)
        {
            if (items == null)
                throw new NullReferenceException();
            return new List<T>(items).AsReadOnly();
        }

        /// <summary>
        /// Converts all elements to the TOutput type. 
        /// </summary>
        public static IEnumerable<TOutput> ConvertAll<TInput, TOutput>(this IEnumerable<TInput> items, Converter<TInput, TOutput> converter)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(converter, "converter");
            foreach (TInput item in items)
                yield return converter(item);
            yield break;
        }
        /// <summary>
        /// Copies the collection to a KeyedCollection&lt;TKey, TItem&gt;
        /// using the itemToKey converter to extract the key from the item;
        /// </summary>
        public static KeyedCollection<TKey, TItem> AsKeyedCollection<TKey, TItem>(this IEnumerable<TItem> items, Converter<TItem, TKey> itemToKey)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(itemToKey, "itemToKey");
            KeyedCollection<TKey, TItem> kc = new KeyedCollection<TKey, TItem>(itemToKey);
            foreach (TItem item in items)
                kc.Add(item);
            return kc;
        }

        /// <summary></summary>
        public static IEnumerable<T> Filter<T>(this IEnumerable<T> items, Predicate<T> filter)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(filter, "filter");
            foreach (T item in items)
                if (filter(item))
                    yield return item;
        }
        /// <summary></summary>
        public static IEnumerable<T> Pipe<T>(this IEnumerable<T> items, Action<T> action)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(action, "action");
            foreach (T item in items)
            {
                action(item);
                yield return item;
            }
        }
        /// <summary></summary>
        public static IEnumerable<T> Pipe<T>(this IEnumerable<T> items, Func<T, T> func)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(func, "func");
            foreach (T item in items)
                yield return func(item);
        }
        /// <summary></summary>
        public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
        {
            if (items == null)
                throw new NullReferenceException();
            Ensure.NotNull(action, "action");
            foreach (T item in items)
                action(item);
        }
        /// <summary></summary>
        public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> items)
        {
            if (items == null)
                throw new NullReferenceException();
            foreach (var e in items)
                foreach (var item in e)
                    yield return item;
        }
        /// <summary></summary>
        public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<IEnumerable<T>>> items)
        {
            if (items == null)
                throw new NullReferenceException();
            foreach (var e in items)
                foreach (var item in e.Flatten())
                    yield return item;
        }
        /// <summary></summary>
        public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<IEnumerable<IEnumerable<T>>>> items)
        {
            if (items == null)
                throw new NullReferenceException();
            foreach (var e in items)
                foreach (var item in e.Flatten())
                    yield return item;
        }

        /// <summary>
        /// forces enumeration through all items in the IEnumerable
        /// </summary>
        public static void Execute<T>(this IEnumerable<T> items)
        {
            if (items == null)
                throw new NullReferenceException();
            foreach (T item in items) ;
        }
        /// <summary></summary>
        public static Pipe<T> AsPipe<T>(this IEnumerable<T> items)
        {
            if (items == null)
                throw new NullReferenceException();
            return new Pipe<T>(items);
        }
        /// <summary></summary>
        public static string ToString<T>(this IEnumerable<T> items, string separator, Converter<T,String> converter)
        {
            var itemStrings = items.ConvertAll(converter).ToArray();
            return String.Join(separator, itemStrings);
        }

    }
    /// <summary></summary>
    public class Pipe<T> : IEnumerable<T>
    {
        private IEnumerable<T> _items;

        private Pipe()
        {
            _items = null;
        }
        /// <summary></summary>
        public Pipe(IEnumerable<T> items)
        {
            if (items == null)
                throw new ArgumentNullException("items", "items is null.");
            if (items is Pipe<T>)
                _items = ((Pipe<T>)items)._items;
            else
                this._items = items;
        }
        /// <summary></summary>
        public static Pipe<T> operator |(Pipe<T> pipe, Converter<T, T> func)
        {
            return new Pipe<T>(pipe.Items.ConvertAll(func));
        }
        /// <summary></summary>
        public static Pipe<T> operator |(Pipe<T> pipe, Predicate<T> func)
        {
            return new Pipe<T>(pipe.Items.Filter(func));
        }
        /// <summary></summary>
        public static Pipe<T> operator |(Pipe<T> pipe, Action<T> action)
        {
            return new Pipe<T>(pipe.Items.Pipe(action));
        }
        /// <summary></summary>
        [Obsolete("This operator exists only to satisfy the compiler, do not use.")]
        public static Pipe<T> operator >=(Pipe<T> pipe, Func<T> producer)
        {
            return Empty;
        }
        /// <summary></summary>
        public static Pipe<T> operator <=(Pipe<T> pipe, Func<T> producer)
        {
            return new Pipe<T>(EnumerableFromProducer(producer));
        }

        /// <summary></summary>
        public Pipe<Tout> Convert<Tout>(Converter<T, Tout> converter)
        {
            return new Pipe<Tout>(Items.ConvertAll(converter));
        }
        /// <summary></summary>
        public Pipe<T> Limit(Predicate<T> limit)
        {
            _items = LimitedEnumerable(Items, limit);
            return this;
        }
        /// <summary></summary>
        public IEnumerator<T> GetEnumerator()
        {
            return Items.GetEnumerator();
        }
        /// <summary></summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Items.GetEnumerator();
        }
        /// <summary></summary>
        public static readonly Pipe<T> Empty = new Pipe<T>();
        /// <summary></summary>
        private static IEnumerable<T> EmptyEnumerable()
        {
            yield break;
        }

        private static IEnumerable<T> LimitedEnumerable(IEnumerable<T> items, Predicate<T> limit)
        {
            foreach (T item in items)
            {
                if (limit(item))
                    yield break;
                yield return item;
            }
        }
        
        private static IEnumerable<T>  EnumerableFromProducer(Func<T> producer)
        {
            while (true)
                yield return producer();
        }

        private IEnumerable<T> Items
        {
            get
            {
                if (_items == null)
                    _items = EmptyEnumerable();
                return _items;
            }
        }

    }
}
